/*
 * Copyright (c) 2025 Huawei Technologies Co., Ltd.
 * openFuyao is licensed under Mulan PSL v2.
 * You can use this software according to the terms and conditions of the Mulan PSL v2.
 * You may obtain a copy of Mulan PSL v2 at:
 *          http://license.coscl.org.cn/MulanPSL2
 * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
 * EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
 * MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
 * See the Mulan PSL v2 for more details.
 */

// Package query 执行具体的查询工作
package query

import (
	"bytes"
	"fmt"
	"os"
	"os/exec"
	"path/filepath"
	"runtime"
	"strings"
	"time"

	"env-check/pkg/config"
	"env-check/pkg/logger"
)

const cmdResMinLen = 3 // ls 命令结果返回参数最少个数

// FileQuery 文件查询器
type FileQuery struct {
	config *config.AppConfig
	logger *logger.Logger
}

// NewQuery 创建新的查询器
func NewQuery(cfg *config.AppConfig, log *logger.Logger) *FileQuery {
	return &FileQuery{
		config: cfg,
		logger: log,
	}
}

// Execute 执行文件查询
func (q *FileQuery) Execute() (*config.QueryResult, error) {
	q.logger.Info("file query start ......")
	q.logger.Info(fmt.Sprintf("query path: %v", q.config.Paths))

	result := &config.QueryResult{
		Timestamp: time.Now(),
		Files:     make([]config.FileInfo, 0),
		Summary:   config.QuerySummary{},
	}

	// 查询所有路径
	for _, path := range q.config.Paths {
		fileInfo := q.queryPath(path)
		result.Files = append(result.Files, fileInfo)

		// 更新汇总信息
		result.Summary.TotalChecked++
		if fileInfo.Exists {
			result.Summary.TotalExists++
			if fileInfo.IsDir {
				result.Summary.TotalDirs++
			} else {
				result.Summary.TotalFiles++
			}
		} else {
			result.Summary.TotalMissing++
		}
	}

	q.logger.Info("file query completed")
	return result, nil
}

// queryPath 查询单个路径
func (q *FileQuery) queryPath(path string) config.FileInfo {
	absPath, err := filepath.Abs(path)
	if err != nil {
		return config.FileInfo{
			Path:   path,
			Exists: false,
			Error:  fmt.Sprintf("unable to obtain the absolute path: %v", err),
		}
	}

	info, err := os.Lstat(absPath)
	if err != nil {
		if os.IsNotExist(err) {
			q.logger.Info(fmt.Sprintf("path not exist: %s", absPath))
			return config.FileInfo{
				Path:   absPath,
				Exists: false,
				Error:  "path not exist",
			}
		}

		q.logger.Warning(fmt.Sprintf("Unable to access path: %s - %v", absPath, err))
		return config.FileInfo{
			Path:   absPath,
			Exists: false,
			Error:  fmt.Sprintf("access err: %v", err),
		}
	}

	user, group, err := getFileOwnerCrossPlatform(absPath)

	if err != nil {
		q.logger.Warning(fmt.Sprintf("Failed to retrieve file owner and group information: %s - %v", absPath, err))
		return config.FileInfo{
			Path:   absPath,
			Exists: true,
			IsDir:  info.IsDir(),
			Error:  fmt.Sprintf("Failed to retrieve file owner and group information: %v", err),
		}
	}

	// 构建文件信息
	fileInfo := config.FileInfo{
		Path:        absPath,
		Exists:      true,
		IsDir:       info.IsDir(),
		Owner:       user,
		Group:       group,
		Permissions: info.Mode().String(),
	}

	// 记录成功信息
	if fileInfo.IsDir {
		q.logger.Warning(fmt.Sprintf("direction exist: %s", absPath))
	} else {
		q.logger.Warning(fmt.Sprintf("file exist: %s", absPath))
	}
	return fileInfo
}

func getFileOwnerCrossPlatform(filePath string) (string, string, error) {
	switch runtime.GOOS {
	case "linux", "darwin", "freebsd":
		return getUnixFileOwner(filePath)
	default:
		return "", "", fmt.Errorf("unsupport od: %s", runtime.GOOS)
	}
}

func getUnixFileOwner(filePath string) (string, string, error) {
	// 使用 ls -l 命令获取文件所有者信息
	cmd := exec.Command("ls", "-ld", filePath)
	var out bytes.Buffer
	cmd.Stdout = &out
	err := cmd.Run()
	if err != nil {
		return "", "", err
	}

	output := out.String()
	parts := strings.Fields(output)
	if len(parts) < cmdResMinLen {
		return "", "", fmt.Errorf("unable to parse ls output")
	}

	// ls -l 输出格式：权限 链接数 所有者 组 大小 日期 文件名
	owner := parts[2]
	group := parts[3]

	return owner, group, nil
}
